﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using PIT.Labs.ProjectRepository.Owl2DotNet.Rdf.AnnotationProperties;
using PIT.Labs.ProjectRepository.Owl2DotNet.Rdf.Classes;
using PIT.Labs.ProjectRepository.Owl2DotNet.Rdf.DataTypes;

namespace PIT.Labs.ProjectRepository.Owl2DotNet.Rdf
{
    /// <summary>
    /// Represents an RDF/OWL XML ontology.
    /// </summary>
    public class RdfOntology : RdfEntityBase, IOntology
    {
        #region "Entitiy Stores"

        Dictionary<string, RdfClass> classes;
        Dictionary<string, RdfObjectProperty> objectProperties;
        Dictionary<string, RdfAnnotationProperty> annotationProperties;
        Dictionary<string, RdfDataType> dataTypes;
        Dictionary<string, RdfIndividual> individuals;
        Dictionary<string, RdfDatatypeProperty> dataProperties;

        RdfClass thing;

        private int RestrictionCounter = 0;
        private Dictionary<string, Dictionary<string, RdfClassRestriction>> restrictionClasses = new Dictionary<string, Dictionary<string, RdfClassRestriction>>();

        #endregion

        /// <summary>
        /// Create a new owl:ontology based on an owl xml node.
        /// </summary>
        /// <param name="node">xml node to create the ontology from</param>
        public RdfOntology(XElement node) : base(node)
        {
            this.RdfOntology = this;
        
            this.classes = new Dictionary<string, RdfClass>();
            this.objectProperties = new Dictionary<string, RdfObjectProperty>();
            this.annotationProperties = new Dictionary<string, RdfAnnotationProperty>();
            this.dataTypes = new Dictionary<string, RdfDataType>();
            this.individuals = new Dictionary<string, RdfIndividual>();
            this.dataProperties = new Dictionary<string, RdfDatatypeProperty>();

            this.DataTypeGeneric = new  RdfDataTypeGeneric(this);

            //XXX Adding them works for now. Maybe find a more general way to deal with rdf standard properties?
            new RdfAnnotationPropertyComment(this);
            new RdfAnnotationPropertyVersionInfo(this);
         }

        /// <summary>
        /// owl:thing class. may be used for scanning through hierrarchy of classes
        /// </summary>
        public IClass Thing
        {
            get {
                if (thing == null)
                {
                    if (!classes.ContainsKey(RdfClassThing.rdfThingUri))
                    {
                        new RdfClassThing(this);
                    }
                    thing = classes[RdfClassThing.rdfThingUri];
                    thing.subClasses = new List<IClass>();
                    thing.superClasses = new List<RdfClass>();
                    thing.subClasses.AddRange(from x in this.classes
                                              where x.Value.SuperClasses.Where(c=>c!=x.Value).Count() == 0 && x.Value.Uri.OriginalString != RdfClassThing.rdfThingUri && x.Value.Uri.OriginalString != RdfClassThing.rdfNothingUri
                                              select (IClass)x.Value);
                }
                return thing;
            }
        }

        /// <summary>
        /// Flat list of all classes
        /// </summary>
        public IEnumerable<IClass> Classes
        {
            get { return from x in classes select (IClass)x.Value; }
        }

        /// <summary>
        /// Get a single class by URI
        /// </summary>
        /// <param name="uri"></param>
        /// <returns></returns>
        public IClass GetClass(Uri uri)
        {
            return classes[uri.OriginalString];
        }
        
        /// <summary>
        /// List of annotation properties
        /// </summary>
        public IEnumerable<IAnnotationProperty> AnnotationProperties
        {
            get { return from x in annotationProperties select (IAnnotationProperty)x.Value; }
        }

        /// <summary>
        /// Get a single annotation property by URI
        /// </summary>
        /// <param name="uri"></param>
        /// <returns></returns>
        public IAnnotationProperty GetAnnotationProperty(Uri uri)
        {
            return annotationProperties[uri.OriginalString];
        }

        /// <summary>
        /// List of data properties
        /// </summary>
        public IEnumerable<IDataProperty> DataProperties
        {
            get { return from x in dataProperties select (IDataProperty)x.Value; }
        }

        /// <summary>
        /// Get a single data property by URI
        /// </summary>
        /// <param name="uri"></param>
        /// <returns></returns>
        public IDataProperty GetDataProperty(Uri uri)
        {
            return dataProperties[uri.OriginalString];
        }

        /// <summary>
        /// List of data types
        /// </summary>
        public IEnumerable<IDataType> DataTypes
        {
            get { return from x in dataTypes select (IDataType)x.Value; }
        }

        /// <summary>
        /// Get a single data type by URI
        /// </summary>
        /// <param name="uri"></param>
        /// <returns></returns>
        public IDataType GetDataType(Uri uri)
        {
            return dataTypes.First().Value;
        }

        /// <summary>
        /// List of individuals
        /// </summary>
        public IEnumerable<IIndividual> Individuals
        {
            get { return from x in individuals select (IIndividual)x.Value; }
        }

        /// <summary>
        /// Get a single individual by type
        /// </summary>
        /// <param name="uri"></param>
        /// <returns></returns>
        public IIndividual GetIndividual(Uri uri)
        {
            return individuals[uri.OriginalString];
        }

        /// <summary>
        /// List of object properties
        /// </summary>
        public IEnumerable<IObjectProperty> ObjectProperties
        {
            get { return from x in objectProperties select (IObjectProperty)x.Value; }
        }

        /// <summary>
        /// Get a single object property by URI
        /// </summary>
        /// <param name="uri"></param>
        /// <returns></returns>
        public IObjectProperty GetObjectProperty(Uri uri)
        {
            return objectProperties[uri.OriginalString];
        }

        #region "Insert Methods"
        internal void Insert(RdfObjectProperty c)
        {
            objectProperties[c.Uri.OriginalString] = c;
        }

        internal void Insert(RdfClass c)
        {
            if (!classes.ContainsKey(c.Uri.OriginalString) || c.Node.Elements().Count() > classes[c.Uri.OriginalString].Node.Elements().Count())
            {
                classes[c.Uri.OriginalString] = c;
            }
        }

        internal void Insert(RdfAnnotationProperty c)
        {
            annotationProperties[c.Uri.OriginalString] = c;
        }

        internal void Insert(RdfIndividual c)
        {
            individuals[c.Uri.OriginalString] = c;
        }

        internal void Insert(RdfDataType c)
        {
            dataTypes[c.Uri.OriginalString] = c;
        }

        internal void Insert(RdfDatatypeProperty c)
        {
            dataProperties[c.Uri.OriginalString] = c;
        }
        #endregion

        #region "RdfMethods"

        internal RdfClass GetClass(String uri)
        {
            return classes[uri];
        }

        internal RdfDataTypeGeneric DataTypeGeneric
        {
            get;
            private set;
        }

        internal RdfClass GetRestrictionClass(string property, string parentClass, XElement node)
        {
            if (!restrictionClasses.ContainsKey(parentClass))
            {
                restrictionClasses[parentClass] = new Dictionary<string, RdfClassRestriction>();
            }
            if (!restrictionClasses[parentClass].ContainsKey(property))
            {
                restrictionClasses[parentClass][property] = new RdfClassRestriction(property, parentClass, ++RestrictionCounter, node, this);
            }

            return restrictionClasses[parentClass][property];
        }

        internal IEnumerable<RdfClassRestriction> GetRestrictionClasses()
        {
            return restrictionClasses.SelectMany(x => x.Value.Values);
        }

        #endregion
    }
}
